﻿//#define USE_DUNGEONBUDDY_DLL

using System;
using System.Collections.Generic;
using System.Linq;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.Helpers;
using Styx.Patchables;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif
{
    public class Stormstout_BreweryHeroic : Dungeon
    {
        #region Overrides of Dungeon

        private readonly WaitTimer _ignoreHoblitesTimer = new WaitTimer(TimeSpan.FromSeconds(20));
        private readonly WaitTimer _killHoblitesTimer = new WaitTimer(TimeSpan.FromSeconds(10));

        public override uint DungeonId
        {
            get { return 469; }
        }

        public override WoWPoint Entrance
        {
            get { return new WoWPoint(-718.4064, 1262.169, 136.4682); }
        }

        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                ret =>
                {
                    var unit = ret.ToUnit();
                    if (unit != null)
                    {
                        if (PartyAnimals.Contains(unit.Entry) && !KillPartyAnimals)
                            return true;

                        if (_hoplites.Contains(unit.Entry) && StyxWoW.Me.Location.DistanceSqr(_hoptallusLoc) > 20 * 20)
                        {
                            if (_ignoreHoblitesTimer.IsFinished)
                            {
                                _ignoreHoblitesTimer.Reset();
                                _killHoblitesTimer.Reset();
                            }
                            if (!_killHoblitesTimer.IsFinished && StyxWoW.Me.IsTank())
                            {
                                return true;
                            }
                            var tank = ScriptHelpers.Tank;
                            if (tank == StyxWoW.Me) // ignore hobblets if they're targeting tank (Me) and continue up towards boss.
                                return IgnoreHopplets && (unit.CurrentTargetGuid == 0 || unit.CurrentTargetGuid == StyxWoW.Me.Guid);
                            return StyxWoW.Me.IsDps() && IgnoreHopplets && tank != null && tank.Distance > DungeonBuddySettings.Instance.FollowingDistance;
                        }
                        if (unit.Entry == Hoptallus && StyxWoW.Me.IsMelee() && unit.CastingSpellId == FurlwindSpellId)
                            return true;

                        if (unit.Entry == YeastyBrewAlemental && ShouldStayInYanZhusMeleeRange)
                            return true;
                    }
                    return false;
                });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                    if (StyxWoW.Me.IsTank())
                    {
                        if ((unit.Entry == SleepyHozenBrawlerId || unit.Entry == HozenPartyAnimalId || unit.Entry == HabaneroBrewId) && unit.DistanceSqr <= 40 * 40 &&
                            Navigator.CanNavigateFully(StyxWoW.Me.Location, unit.Location, 4) && KillPartyAnimals)
                            outgoingunits.Add(unit);
                    }
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == HabaneroBrewId)
                        priority.Score += 400;

                    if (BopperIds.Contains(unit.Entry) && StyxWoW.Me.IsDps())
                        priority.Score += 600;

                    if (HopperIds.Contains(unit.Entry) && StyxWoW.Me.IsDps())
                        priority.Score += 500;

                    // ignore these and stay in melee of Yan-Zhu
                    if (unit.Entry == YeastyBrewAlemental)
                    {
                        if (ShouldStayInYanZhusMeleeRange && unit.Location.Distance(_yanZhu.Location) > _yanZhu.MeleeRange())
                            priority.Score -= 500;
                        else
                            priority.Score += 500;
                    }
                }
            }
        }

        #endregion

        private const uint RollingBarrelId = 56682;
        private const uint HozenPartyAnimalId = 56927;
        private const uint SleepyHozenBrawlerId = 56863;
        private const uint HabaneroBrewId = 56731;
        private const uint MysteriouslyShakingKegId = 211138;
        private const uint Hoptallus = 56717;
        private const int FurlwindSpellId = 112992;
        private const uint GushingBrewId = 59394;
        private const int CarbonationId = 56746;
        private const uint FizzyBrewAlemental = 56748;
        private const uint YeastyBrewAlemental = 59494;
        private const int FermentHealSpellId = 114451;
        private const int FermentSpellId = 106859;
        private const uint WallOfSudsVehicle = 59510;
        private const uint YanZhuTheUncasked = 56717;
        private const uint UncleGaoId = 59074;
        private const uint ChenStormstoutId = 64361;
        private const int ExplosiveBrew = 116027;
        private const uint FizzyBubbleId = 59799;
        const int FizzyBubblesSpellId = 114459;
        const int CarbonationSpellId = 114386;

        private static readonly uint[] BopperIds = new uint[] { 59426, 59551 };

        private static readonly uint[] HoplingIds = new uint[] { 59459, 59460, 59461 };

        private static readonly uint[] HopperIds = new uint[] { 59464, 56718 };
        private readonly WoWPoint _exitBarrelLoc = new WoWPoint(-697.1378, 1259.298, 162.7813);

        private readonly uint[] _hoplites = BopperIds.Concat(HoplingIds.Concat(HopperIds)).ToArray();
        private readonly WoWPoint _hoptallusLoc = new WoWPoint(-696.0826, 1259.851, 162.7818);

        private static readonly uint[] PartyAnimals = new uint[] { SleepyHozenBrawlerId, HozenPartyAnimalId, HabaneroBrewId };

        private DateTime HozenPartyAnimalsDistruptedUpdatedTime;
        private int _hozenPartyAnimalsDistrupted;

        private WoWUnit _ookook;
        private WoWUnit _yanZhu;

        private static LocalPlayer Me
        {
            get { return StyxWoW.Me; }
        }

        private int HozenPartyAnimalsDistrupted
        {
            get
            {
                // Throttle the Lua call for efficiency
                if (DateTime.Now - HozenPartyAnimalsDistruptedUpdatedTime > TimeSpan.FromMilliseconds(1000))
                {
                    _hozenPartyAnimalsDistrupted = Lua.GetReturnVal<int>("return UnitPower('player',10)", 0);
                    HozenPartyAnimalsDistruptedUpdatedTime = DateTime.Now;
                }
                return _hozenPartyAnimalsDistrupted;
            }
        }

        private bool KillPartyAnimals
        {
            get { return ScriptHelpers.IsBossAlive("Ook-Ook") && HozenPartyAnimalsDistrupted < 40; }
        }


        private bool IgnoreHopplets
        {
            get { return ScriptHelpers.IsBossAlive("Hoptallus") && ObjectManager.GetObjectsOfType<WoWGameObject>().Any(g => g.Entry == MysteriouslyShakingKegId && g.State == WoWGameObjectState.Ready); }
        }

        private bool ShouldJumpForWallsOfSuds
        {
            get
            {
                var sudsWalls = ObjectManager.GetObjectsOfType<WoWUnit>().Where(u => u.Entry == WallOfSudsVehicle).OrderBy(u => u.DistanceSqr).ToArray();
                if (sudsWalls.Length == 0) return false;
                var myLoc = StyxWoW.Me.Location;
                return (from sudsWall in sudsWalls
                        let start = WoWMathHelper.CalculatePointAtSide(sudsWall.Location, sudsWall.Rotation, 10, true)
                        let end = WoWMathHelper.CalculatePointAtSide(sudsWall.Location, sudsWall.Rotation, 10, false)
                        where myLoc.GetNearestPointOnLine(start, end).DistanceSqr(myLoc) <= 15 * 15
                        select start).Any();
            }
        }

        private bool ShouldStayInYanZhusMeleeRange
        {
            get
            {
                var tank = ScriptHelpers.Tank;
                return _yanZhu != null && _yanZhu.IsValid && _yanZhu.Combat && (tank == null || tank.IsMe || tank.Location.Distance(_yanZhu.Location) > _yanZhu.MeleeRange() && StyxWoW.Me.IsMelee());
            }
        }

        private WoWPoint FermentInterceptLoc
        {
            get
            {
                if (StyxWoW.Me.HasAura(FermentHealSpellId))
                    return WoWPoint.Zero;

                return (from yeasty in ObjectManager.GetObjectsOfType<WoWUnit>()
                        where yeasty.HasAura(FermentSpellId)
                        let target =
                            ObjectManager.GetObjectsOfType<WoWUnit>(false, false).FirstOrDefault(u => u.GetAllAuras().Any(a => a.SpellId == FermentHealSpellId && a.CreatorGuid == yeasty.Guid))
                        where target != null && (ShouldStayInYanZhusMeleeRange && target.Entry == YanZhuTheUncasked || !ShouldStayInYanZhusMeleeRange)
                        select StyxWoW.Me.Location.GetNearestPointOnSegment(yeasty.Location, target.Location)).FirstOrDefault();
            }
        }

        [ObjectHandler(213795, "Stormstout Secrets", ObjectRange = 30)]
        public Composite StormstoutSecretsHandler()
        {
            const uint StormstoutSecretsId = 213795; // quest object

            const int FamilySecretsQuestId = 31324;
            return
                new Decorator<WoWGameObject>(
                    book =>
                    StyxWoW.Me.QuestLog.ContainsQuest(FamilySecretsQuestId) && !StyxWoW.Me.QuestLog.GetQuestById(FamilySecretsQuestId).IsCompleted && Targeting.Instance.IsEmpty() &&
                    !ScriptHelpers.WillPullAggroAtLocation(book.Location),
                    new Sequence(
                        ScriptHelpers.CreateInteractWithObjectContinue(StormstoutSecretsId, 2),
                        new WaitContinue(3, ctx => LootFrame.Instance.IsVisible, new ActionAlwaysSucceed()),
                        new DecoratorContinue(
                            ctx => LootFrame.Instance.IsVisible, new Sequence(new Action(ctx => Logger.Write("Looting Stormstout Secrets")), new Action(ctx => LootFrame.Instance.LootAll())))));
        }

        private IEnumerable<WoWPoint> GetBloatedPartyMemberRunFromLoc()
        {
            return (from member in StyxWoW.Me.PartyMembers.Where(u => u.HasAura("Bloat")).OrderBy(u => u.DistanceSqr)
                    let loc = member.Location
                    let leftLoc = WoWMathHelper.CalculatePointAtSide(loc, member.Rotation, 8, false)
                    let rightLoc = WoWMathHelper.CalculatePointAtSide(loc, member.Rotation, 8, true)
                    select loc.GetNearestPointOnSegment(rightLoc, leftLoc)).ToArray();
        }

        [EncounterHandler(56637, "Ook-Ook")]
        public Composite OokOokEncounter()
        {
            WoWUnit rollingBarrel = null;

            return new PrioritySelector(
                ctx => _ookook = ctx as WoWUnit,
                // handle combat behavior
                new PrioritySelector(
                    ScriptHelpers.GetBehindUnit(ctx => !StyxWoW.Me.IsTank() && _ookook.IsSafelyFacing(StyxWoW.Me, 60), () => _ookook),
                // ScriptHelpers.CreateTankFaceAwayGroupUnit(30),
                // handle barrels
                    new Decorator(
                        ctx => StyxWoW.Me.IsDps(),
                        new PrioritySelector(
                            ctx =>
                            rollingBarrel =
                            ObjectManager.GetObjectsOfType<WoWUnit>().Where(u => u.Entry == RollingBarrelId && u.DistanceSqr <= 12 * 12 && u.CharmedByUnitGuid == 0).OrderBy(u => u.DistanceSqr).
                                FirstOrDefault(),
                // if I'm riding a barrel then move towards boss.
                            new Decorator(ctx => StyxWoW.Me.IsOnTransport, new Action(ctx => Navigator.PlayerMover.MoveTowards(_ookook.Location))),
                // If I'm a Dps then hop on any nearby rolling barrels 
                            new Decorator(ctx => rollingBarrel != null, new Action(ctx => rollingBarrel.Interact()))))));
        }

        [EncounterHandler(59539, "Big Ol' Hammer", Mode = CallBehaviorMode.Proximity, BossRange = 10)]
        public Composite BigOlHammerEncounter()
        {
            WoWUnit hammer = null;
            return new PrioritySelector(
                ctx => hammer = ctx as WoWUnit,
                new Decorator(
                // can have a max of 10 stacks of Smash! debuf. each hammer gives 3
                    ctx => (!StyxWoW.Me.HasAura("Smash!") || StyxWoW.Me.Auras["Smash!"].StackCount <= 7) && !StyxWoW.Me.IsHealer(), ScriptHelpers.CreateInteractWithObject(() => hammer)));
        }

        [EncounterHandler(0, "Root")]
        public Composite RootEncounter()
        {
            return new PrioritySelector(
                new Decorator(ctx => StyxWoW.Me.HasAura("How Did I Get Here?"), new Action(ctx => Navigator.PlayerMover.MoveTowards(_exitBarrelLoc))),
                ScriptHelpers.CreateRunAwayFromBad(
                    ctx => !Me.HasAura("Smash!"), 10, u => HopperIds.Contains(u.Entry) && u.ToUnit().CastingSpellId == ExplosiveBrew && u.ToUnit().CurrentCastTimeLeft <= TimeSpan.FromSeconds(3)),
                //casted by Fizzy Brew Alemental
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 6, CarbonationId, FizzyBrewAlemental),
                // brew gushing out of barrels. don't stand in it.
                ScriptHelpers.CreateRunAwayFromBad(ctx => StyxWoW.Me.Combat && Math.Abs(StyxWoW.Me.Z - 139) <= 10, 4, GushingBrewId),
                // run from bloated party members.
                ScriptHelpers.CreateRunAwayFromLocation(ctx => GetBloatedPartyMemberRunFromLoc().Any(), 3, () => GetBloatedPartyMemberRunFromLoc().FirstOrDefault()),
                ScriptHelpers.CreateRunAwayFromLocation(ctx => GetBloatedPartyMemberRunFromLoc().Any(), 3, () => GetBloatedPartyMemberRunFromLoc().LastOrDefault()),
                // jump to get rid of blackout brew debuf.
                new Decorator(
                    ctx => StyxWoW.Me.HasAura("Blackout Brew") &&
                        !Me.HasAura(FizzyBubblesSpellId) &&
                        !Me.HasAura(CarbonationSpellId) &&
                        (!Me.IsHealer() || Me.IsHealer() && Me.Auras["Blackout Brew"].StackCount < 9 && Me.PartyMembers.All(p => p.HealthPercent > 50)),
                    new Action(ctx =>
                                       {
                                           Lua.DoString("JumpOrAscendStart()");
                                           Lua.DoString("AscendStop()");
                                           return RunStatus.Failure;
                                       })),
                CreateHoppletBehavior(),
                // dodge barrels if not boarding them and nobody is riding them
                ScriptHelpers.CreateRunAwayFromBad(
                    ctx => _ookook == null || !_ookook.IsValid || !_ookook.Combat || _ookook.Combat && !StyxWoW.Me.IsDps(),
                    4,
                    u => u.Entry == RollingBarrelId && ((WoWUnit)u).CharmedByUnitGuid == 0,
                    u =>
                    {
                        var start = u.Location;
                        return Me.Location.GetNearestPointOnSegment(start, start.RayCast(WoWMathHelper.NormalizeRadian(u.Rotation), 20));
                    }));
        }

        private Composite CreateHoppletBehavior()
        {
            const int explosiveBrewSpellId = 114291;
            return
                new PrioritySelector(
                    new Decorator(
                        ctx => StyxWoW.Me.HasAura("Smash!") && ScriptHelpers.GetUnfriendlyNpsAtLocation(() => StyxWoW.Me.Location, 6, u => _hoplites.Contains(u.Entry)).Any(),
                        new Action(
                            ctx =>
                            {
                                Lua.DoString("ExtraActionButton1:Click()");
                                return RunStatus.Failure; // we can do other stuff while casting this.
                            })),
                // run away before hopper explodes.
                    ScriptHelpers.CreateRunAwayFromBad(
                        ctx => true,
                        10,
                        u => HopperIds.Contains(u.Entry) && ((WoWUnit)u).CastingSpellId == explosiveBrewSpellId && ((WoWUnit)u).CurrentCastTimeLeft <= TimeSpan.FromMilliseconds(2000)));
        }


        [EncounterHandler(56717, "Hoptallus")]
        public Composite HoptallusEncounter()
        {
            WoWUnit boss = null;
            const int carrotBreathSpellId = 112944;

            var insideDoorLoc = new WoWPoint(-700.3579, 1275.827, 162.7954);
            var roomCenterLoc = new WoWPoint(-696.4132, 1258.904, 162.7954);

            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                // make sure we're inside the door!
                // make sure we're inside the door!
                new Decorator(ctx => StyxWoW.Me.Y > 1280, new Action(ctx => Navigator.MoveTo(insideDoorLoc))),
                // Run away from furlwind
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, () => roomCenterLoc, 20, 10, u => u.Entry == Hoptallus && ((WoWUnit)u).CastingSpellId == FurlwindSpellId),
                ScriptHelpers.GetBehindUnit(ctx => boss != null && boss.CastingSpellId == carrotBreathSpellId && boss.IsSafelyFacing(StyxWoW.Me, 90), () => boss),
                new Decorator(ctx => StyxWoW.Me.CurrentTarget == boss && !boss.IsCasting, ScriptHelpers.CreateTankUnitAtLocation(() => roomCenterLoc, 5)));
        }

        // handles waiting for boss to spawn.
        [EncounterHandler(59479, "Yan-Zhu the Uncasked", Mode = CallBehaviorMode.CurrentBoss)]
        public Composite YanZhuTheUncaskedSpawnEncounter()
        {
            var roomCenterLoc = new WoWPoint(-703.0988, 1163.54, 166.1415);

            return new PrioritySelector(
                ctx => _yanZhu = ctx as WoWUnit,
                // walls of suds.
                new Decorator(
                    ctx =>
                    _yanZhu == null && Targeting.Instance.IsEmpty() && ObjectManager.GetObjectsOfType<WoWUnit>().All(u => u.Entry != ChenStormstoutId) && StyxWoW.Me.IsTank() &&
                    StyxWoW.Me.Location.DistanceSqr(roomCenterLoc) <= 20 * 20,
                    new ActionAlwaysSucceed()));
        }

        [ObjectHandler(211137, "Sliding Door", ObjectRange = 4)]
        public Composite SlidingDoorHandler()
        {
            return new PrioritySelector(
                new Decorator<WoWGameObject>(door => door.State == WoWGameObjectState.Ready,
                    new Helpers.Action<WoWGameObject>(door =>
                                                  {
                                                      door.Interact();
                                                      return RunStatus.Failure;
                                                  }))
                );
        }

        [EncounterHandler(59479, "Yan-Zhu the Uncasked")]
        public Composite YanZhuTheUncaskedEncounter()
        {
            WoWUnit fizzyBubble = null;
            var jumpTimer = new WaitTimer(TimeSpan.FromSeconds(2));
            return new PrioritySelector(
                ctx =>
                {
                    fizzyBubble = ObjectManager.GetObjectsOfType<WoWUnit>().Where(u => u.Entry == FizzyBubbleId).OrderBy(u => u.DistanceSqr).FirstOrDefault();
                    return _yanZhu = ctx as WoWUnit;
                },
                // Handle carbonation.
                new Decorator(ctx => fizzyBubble != null && !Me.HasAura(FizzyBubblesSpellId),
                    new PrioritySelector(
                        new Decorator(ctx => fizzyBubble.Distance2D <= 4,
                            new Action(ctx => fizzyBubble.Interact())),

                        new Decorator(ctx => fizzyBubble.Distance2D > 4,
                            new Action(ctx => Navigator.MoveTo(fizzyBubble.Location)))
                        )),
                new Decorator(ctx => Me.HasAura(FizzyBubblesSpellId) && jumpTimer.IsFinished,
                    new PrioritySelector(
                        new Decorator(ctx => Me.HasAura(CarbonationSpellId) || !Me.IsFlying,
                            new Action(ctx =>
                                           {
                                               Lua.DoString("JumpOrAscendStart()");
                                               Lua.DoString("AscendStop()");
                                               var loc = Me.Location;
                                               loc.Z += 6;
                                               Navigator.PlayerMover.MoveTowards(loc);
                                               jumpTimer.Reset();
                                           }))
                        )),

                // walls of suds.
                new Decorator(
                    ctx => ShouldJumpForWallsOfSuds,
                    new Sequence(
                        new Action(ctx => WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend)),
                        new Action(ctx => Logger.Write("Jumping to avoid wall of suds")),
                        new Action(ctx => WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend)))),
                new Decorator(
                    ctx => ShouldStayInYanZhusMeleeRange && _yanZhu.Distance2DSqr > 4 * 4,
                    new Sequence(
                        new Action(ctx => Logger.Write("Moving into Yan-Zhu's Melee Range")),
                        new DecoratorContinue(
                            ctx => _yanZhu.Distance2DSqr < 10 * 10, new Action(ctx => Navigator.PlayerMover.MoveTowards(WoWMathHelper.CalculatePointFrom(StyxWoW.Me.Location, _yanZhu.Location, 3)))),
                        new DecoratorContinue(
                            ctx => _yanZhu.Distance2DSqr >= 10 * 10, ScriptHelpers.CreateMoveToContinue(() => WoWMathHelper.CalculatePointFrom(StyxWoW.Me.Location, _yanZhu.Location, 3))))),
                new PrioritySelector(
                    ctx => FermentInterceptLoc,
                    new Decorator(
                        ctx => (WoWPoint)ctx != WoWPoint.Zero,
                        new Sequence(new Action(ctx => Logger.Write("Intercepting Ferment")), new Action(ctx => Navigator.PlayerMover.MoveTowards((WoWPoint)ctx))))));
        }
    }
}